//
// Created by DS331B3 on 2023/3/11.
//

#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <cerrno>
#include <unistd.h>
#include <cstring>
#include <iostream>
#include <chrono>
#include "header/MemoryFile.h"
#include "header/AndroidLog.h"

using std::chrono::high_resolution_clock;
using std::chrono::milliseconds;

#define FOURM 1024 * 1024 * 4

MemoryFile::MemoryFile(const char &path , int maxSize) {
    logFilePath = &path ;
    pageSize = getpagesize() ;
    ptr = 0 ;
    actualSize = 0 ;
    this->maxSize = maxSize ;
    reloadFromFile() ;
}

bool MemoryFile::writeLog(const char *tag, const char *msg) {
    int logSize = 0 ;
    int tagSize = strlen(tag) ;
    int msgSize = strlen(msg) ;
    int intSize = 4 ;
    logSize = tagSize + msgSize + intSize * 2 ;
    if (actualSize + logSize > fileSize) {
        grow() ;
    }

    writeInt(tagSize) ;
    writeStr(tag , tagSize) ;
    writeInt(msgSize) ;
    writeStr(msg , msgSize) ;

    memcpy(ptr , &actualSize , 4) ;
    return true ;
}

void MemoryFile::writeInt(int &value) {
    memcpy(ptr + actualSize , &value , 4) ;
    actualSize += 4 ;
}

void MemoryFile::writeStr(const char *value , int &size) {
    memcpy(ptr + actualSize , value , size) ;
    actualSize += size ;
}

bool MemoryFile::smmap() {
    ptr = (uint8_t*)mmap(ptr , fileSize , PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0) ;

    if (ptr == MAP_FAILED) {
        LOGE("fail to mmap [%s], %s", logFilePath.c_str(), strerror(errno));
        ptr = nullptr;
        return false;
    }
    return true ;
}

bool MemoryFile::grow() {
    if (fileSize >= FOURM) {
        close(fd) ;
        std::string newLogFilePath = logFilePath + ".bak" ;
        int renameResult = rename(logFilePath.c_str() , newLogFilePath.c_str()) ;
        if (renameResult == 0) {
            munmap(ptr , fileSize) ;
            reloadFromFile() ;
            return true ;
        }

    }
    int oldSize = fileSize ;

    if (!truncate(fileSize * 2)) {
        fileSize = oldSize ;
        return false ;
    }

    smmap() ;

    return true ;
}


bool MemoryFile::mkPath(std::string path) {

    struct stat st = {};
    bool done = false;
    char *slash = new char[path.size()];
    memcpy(slash , path.c_str() , path.size()) ;
    // 逐级检查或创建文件夹
    while (!done) {
        // 截取一层路径
        slash += strspn(slash, "/");
        slash += strcspn(slash, "/");
        // 判断路径是否结束
        done = (*slash == '\0');
        *slash = '\0';

        // stat获取文件状态
        if (stat(path.c_str(), &st) != 0) {
            // 获取状态失败，ENOENT表示文件不存在。如果不是由于文件不存在导致的失败，则mkPath失败。否则进行
            // 创建目录，若创建失败，则也mkPath失败。
            if (errno != ENOENT || mkdir(path.c_str(), 0777) != 0) {
                LOGE("fail to mkPath %s : %s", path.c_str(), strerror(errno));
                return false;
            }
        } else if (!S_ISDIR(st.st_mode)) {
            // 获取状态成功，判断path非目录，则也失败
            LOGE("fail to mkPath %s : %s", path.c_str(), strerror(ENOTDIR));
            return false;
        }

        *slash = '/';
    }

    return true;
}

//open->ftruncate->mmap
void MemoryFile::reloadFromFile() {
    int lastSlash = logFilePath.find_last_of('/') + 1 ;
    std::string  path = logFilePath.substr(0 , lastSlash) ;
    mkPath(path) ;
    fd = open(logFilePath.c_str() , O_RDWR | O_CREAT | O_CLOEXEC, S_IRWXU) ;
    if (fd < 0) {
        LOGD("faild to open %s" , logFilePath.c_str()) ;
    } else {
        struct stat st = {} ;
        if (fstat(fd , &st) != -1) {
            fileSize = (int)st.st_size ;
        }
        bool newFile = fileSize == 0 ;
        if (fileSize < pageSize || fileSize % pageSize != 0) {
            int roundSize = ((fileSize / pageSize) + 1 ) * pageSize ;
            if (truncate(roundSize)) {
                fileSize = roundSize ;
            }
        }

        smmap() ;

        if (newFile) {
            actualSize = 0 ;
            memcpy(ptr , &actualSize , 4) ;
        } else {
            memcpy(&actualSize , ptr , 4) ;
        }
        actualSize = actualSize + 4 ;
    }
}

bool MemoryFile::zeroFillSize(int size) {
    if (lseek(fd, static_cast<off_t>(actualSize), SEEK_SET) < 0) {
        LOGE("fail to lseek fd[%d], error:%s", fd, strerror(errno));
        return false;
    }

    static const char zeroBuffer[4096] = {0};

    while (size >= sizeof(zeroBuffer)) {
        if (write(fd, zeroBuffer, sizeof(zeroBuffer)) < 0) {
            LOGE("fail to write fd[%d], error:%s", fd, strerror(errno));
            return false;
        }
        size -= sizeof(zeroBuffer);
    }
    if (size > 0) {
        if (write(fd, zeroBuffer, size) < 0) {
            LOGE("fail to write fd[%d], error:%s", fd, strerror(errno));
            return false;
        }
    }
    return true;
}

bool MemoryFile::truncate(int size) {
    if (size > 1024 * 1024 * 4) {
        return false ;
    }
    if (fd < 0) {
        return false ;
    }

    if (fileSize == size) {
        return true ;
    }

    auto oldSize = fileSize ;

    fileSize = size ;

    if (ftruncate(fd , fileSize) != 0) {
        fileSize = oldSize ;
        return false ;
    }

    high_resolution_clock::time_point beginTime = high_resolution_clock::now();
    zeroFillSize(fileSize - oldSize) ;
    high_resolution_clock::time_point  endTime = high_resolution_clock::now() ;
    milliseconds timeInterval = std::chrono::duration_cast<milliseconds>(endTime - beginTime);
    LOGD("zeroFile consume = %lldms" , timeInterval.count()) ;

    munmap(ptr , oldSize) ;


    return true;
}
